/*
* Copyright (c) Huawei Technologies Co., Ltd. 2019-2022. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Description:
* Author: huawei
* Create: 2019-10-15
*/

#include <linux/io.h>
#include <linux/ioctl.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/mutex.h>
#include <linux/cdev.h>
#include <linux/delay.h>
#include <linux/notifier.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/atomic.h>
#include <linux/poll.h>
#include <linux/sort.h>
#include <linux/vmalloc.h>

#include "davinci_api.h"
#include "dms_cmd_def.h"
#include "urd_init.h"
#include "urd_define.h"
#include "urd_notifier.h"
#include "urd_feature.h"
#include "urd_kv.h"
#include "securec.h"

#ifndef AOS_LLVM_BUILD
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 20, 0)
#include <linux/namei.h>
#endif
#else
#include <linux/namei.h>
#endif

STATIC int dms_open(struct inode* inode, struct file* filep)
{
    struct urd_file_private_stru* file_private = NULL;
    if (filep == NULL) {
        return -EINVAL;
    }

    if (!try_module_get(THIS_MODULE)) {
        return -EBUSY;
    }

    file_private =
        (struct urd_file_private_stru*)kzalloc(sizeof(struct urd_file_private_stru), GFP_KERNEL | __GFP_ACCOUNT);
    if (file_private == NULL) {
        dms_err("kzalloc failed. (size=%lu)\n", sizeof(struct urd_file_private_stru));
        module_put(THIS_MODULE);
        return -ENOMEM;
    }
    file_private->owner_pid = current->tgid;
    filep->private_data = (void *)file_private;
    atomic_set(&file_private->work_count, 0);
    return 0;
}

STATIC int dms_release(struct inode* inode, struct file* filep)
{
    struct urd_file_private_stru* file_private = NULL;

    if (filep == NULL) {
        return -EINVAL;
    }
    module_put(THIS_MODULE);

    file_private = filep->private_data;
    if (file_private == NULL) {
        return -EBADFD;
    }
    urd_notifier_call(URD_NOTIFIER_RELEASE, file_private);

    kfree(file_private);
    file_private = NULL;
    return 0;
}

unsigned int dms_msg_poll(struct file* filep, struct poll_table_struct* wait)
{
    return POLLERR;
}

int dms_make_feature_key(struct dms_ioctl_arg* ctl_arg, DMS_FEATURE_ARG_S* arg)
{
    int ret;
    char* filter = NULL;
    if ((ctl_arg->filter_len > FILTER_MAX_LEN) || ((ctl_arg->filter_len == 0) && (ctl_arg->filter != NULL))) {
        dms_err("filter_len is error. (filter_len=%u)", ctl_arg->filter_len);
        return -EINVAL;
    }
    arg->key = (char*)kzalloc(KV_KEY_MAX_LEN + 1, GFP_KERNEL | __GFP_ACCOUNT);
    if (arg->key == NULL) {
        dms_err("kzalloc failed. (size=%d)\n", KV_KEY_MAX_LEN);
        return -ENOMEM;
    }
    if (ctl_arg->filter != NULL) {
        filter = kzalloc(ctl_arg->filter_len + 1, GFP_KERNEL | __GFP_ACCOUNT);
        if (filter == NULL) {
            dms_err("kzalloc failed. (size=%u)\n", ctl_arg->filter_len + 1);
            kfree(arg->key);
            arg->key = NULL;
            return -ENOMEM;
        }
        ret = copy_from_user(filter, (void*)((uintptr_t)ctl_arg->filter), ctl_arg->filter_len);
        if (ret != 0) {
            dms_err("copy_from_user_safe failed. (ret=%d; size=%u)\n", ret, ctl_arg->input_len);
            kfree(arg->key);
            arg->key = NULL;
            kfree(filter);
            filter = NULL;
            return ret;
        }
    }
    ret = dms_feature_make_key(ctl_arg->main_cmd, ctl_arg->sub_cmd, filter, arg->key, KV_KEY_MAX_LEN + 1);
    if (ret != 0) {
        dms_err(
            "make feature's key failed. (ret=%d; main_cmd=%u; sub_cmd=%u)\n", ret, ctl_arg->main_cmd, ctl_arg->sub_cmd);
        kfree(arg->key);
        arg->key = NULL;
    }
    if (filter != NULL) {
        kfree(filter);
        filter = NULL;
    }
    return ret;
}

int dms_make_feature_input(struct dms_ioctl_arg* ctl_arg, DMS_FEATURE_ARG_S* arg)
{
    int ret;
    arg->input = NULL;
    arg->input_len = 0;

    /* means no input data */
    if ((ctl_arg->input_len == 0) && (ctl_arg->input == NULL)) {
        return 0;
    }

    if ((ctl_arg->input_len != 0) && (ctl_arg->input != NULL) && (ctl_arg->input_len <= DMS_MAX_INPUT_LEN)) {
        arg->input = kmalloc(ctl_arg->input_len, GFP_KERNEL | __GFP_ACCOUNT);
        if (arg->input == NULL) {
            dms_err("kzalloc failed. (size=%u)\n", ctl_arg->input_len);
            return -ENOMEM;
        }
        arg->input_len = ctl_arg->input_len;
        ret = copy_from_user(arg->input, (void*)((uintptr_t)ctl_arg->input), ctl_arg->input_len);
        if (ret != 0) {
            dms_err("copy_from_user_safe failed. (ret=%d; size=%u)\n", ret, ctl_arg->input_len);
            kfree(arg->input);
            arg->input = NULL;
            return ret;
        }
        return 0;
    } else {
        dms_err("Input para error. (input_len=%u)\n", ctl_arg->input_len);
        return -EINVAL;
    }
}

#define DMS_OUTPUT_2M_LIMIT (2 * 1024 * 1024)
// apply high-capacity memory for output
STATIC int dms_make_feature_max_output(struct dms_ioctl_arg* ctl_arg, DMS_FEATURE_ARG_S* arg)
{
    if (ctl_arg->output_len > DMS_OUTPUT_2M_LIMIT) {
        dms_err("Output len is larger than 2M limit. (output_len=%u)\n", ctl_arg->output_len);
        return -EINVAL;
    }

    arg->output = kzalloc(ctl_arg->output_len, GFP_KERNEL | __GFP_ACCOUNT);
    if (arg->output == NULL) {
        dms_err("kzalloc failed. (size=%u)\n", ctl_arg->output_len);
        return -ENOMEM;
    }
    arg->output_len = ctl_arg->output_len;
    return 0;
}

// if need high-capacity memory(greater than DMS_MAX_OUTPUT_LEN) return true
static bool dms_is_need_high_capacity_memory(struct dms_ioctl_arg* ctl_arg)
{
    //  get history fault event info need high-capacity memory
    if (ctl_arg->main_cmd == DMS_MAIN_CMD_BASIC &&
        ctl_arg->sub_cmd == DMS_SUBCMD_GET_HISTORY_FAULT_EVENT) {
        return true;
    }
    return false;
}

int dms_make_feature_output(struct dms_ioctl_arg* ctl_arg, DMS_FEATURE_ARG_S* arg)
{
    arg->output = NULL;
    arg->output_len = 0;

    /* means no output data */
    if (ctl_arg->output_len == 0) {
        return 0;
    }

    // need high-capacity memory, maybe greater than DMS_MAX_OUTPUT_LEN
    if (dms_is_need_high_capacity_memory(ctl_arg)) {
        return dms_make_feature_max_output(ctl_arg, arg);
    }

    if (ctl_arg->output_len <= DMS_MAX_OUTPUT_LEN) {
        arg->output = kzalloc(ctl_arg->output_len, GFP_KERNEL | __GFP_ACCOUNT);
        if (arg->output == NULL) {
            dms_err("kzalloc failed. (size=%u)\n", ctl_arg->output_len);
            return -ENOMEM;
        }
        arg->output_len = ctl_arg->output_len;
        return 0;
    } else {
        dms_err("Output len error. (output_len=%u)\n", ctl_arg->output_len);
        return -EINVAL;
    }
}

int dms_proc_feature_output(struct dms_ioctl_arg* ctl_arg, DMS_FEATURE_ARG_S* arg)
{
    int ret;
    if (ctl_arg->output != NULL) {
        ret = copy_to_user(ctl_arg->output, (void*)((uintptr_t)arg->output), ctl_arg->output_len);
        if (ret != 0) {
            dms_err("copy_from_user_safe failed. (size=%u)\n", ctl_arg->output_len);
            return ret;
        }
    }

    return 0;
}

STATIC void dms_free_msg(DMS_FEATURE_ARG_S* arg, struct dms_ioctl_arg* ctl_arg)
{
    if (arg->key != NULL) {
        kfree(arg->key);
        arg->key = NULL;
    }
    if (arg->input != NULL) {
        kfree(arg->input);
        arg->input = NULL;
    }
    if (arg->output != NULL) {
        kfree(arg->output);
        arg->output = NULL;
    }
    return;
}

int dms_make_up_msg_arg(struct dms_ioctl_arg* ctl_arg, DMS_FEATURE_ARG_S* arg)
{
    int ret;
    ret = dms_make_feature_key(ctl_arg, arg);
    if (ret != 0) {
        return ret;
    }

    ret = dms_make_feature_input(ctl_arg, arg);
    if (ret != 0) {
        dms_free_msg(arg, ctl_arg);
        return ret;
    }

    ret = dms_make_feature_output(ctl_arg, arg);
    if (ret != 0) {
        dms_free_msg(arg, ctl_arg);
        return ret;
    }
    return 0;
}

STATIC int dms_cmd_process(struct dms_ioctl_arg* ctl_arg)
{
    int ret;
    DMS_FEATURE_ARG_S arg = {0};
    /* make up call arg */
    ret = dms_make_up_msg_arg(ctl_arg, &arg);
    if (ret != 0) {
        dms_err("Make up message arg failed. (ret=%d)\n", ret);
        return ret;
    }

    /* call feature process */
    ret = dms_feature_process(&arg);
    if (ret != 0) {
        /* do not print err info */
        goto out;
    }
    /* copy out data to user */
    ret = dms_proc_feature_output(ctl_arg, &arg);
    if (ret != 0) {
        dms_err("Process output failed. (ret=%d; size=%u)\n", ret, ctl_arg->output_len);
    }

out:
    dms_free_msg(&arg, ctl_arg);
    return ret;
}

STATIC long dms_ioctl(struct file* filep, unsigned int cmd, unsigned long arg)
{
    int ret;
    struct dms_ioctl_arg ctl_arg = {0};
    if ((filep == NULL) || (arg == 0)) {
        dms_err("Invalid file_private_data.\n");
        return -EINVAL;
    }

    /* copy ioctl arg from user */
    ret = copy_from_user(&ctl_arg, (void*)((uintptr_t)arg), sizeof(struct dms_ioctl_arg));
    if (ret != 0) {
        dms_err("copy_from_user_safe failed.\n");
        return ret;
    }
    switch (cmd) {
        case DMS_IOCTL_CMD:
        case DMS_GET_CHIP_INFO_CMD:
        case DMS_GET_FAULT_EVENT:
        case DMS_GET_HISTORY_FAULT_EVENT:
        case DMS_GET_EVENT_CODE:
        case DMS_GET_CPU_INFO:
        case DMS_SENSOR_NODE_REGISTER:
        case DMS_SENSOR_NODE_UNREGISTER:
        case DMS_SENSOR_NODE_UPDATE_VAL:
            ret = dms_cmd_process(&ctl_arg);
            break;
        default:
            dms_err("Invalid commmad. (command=%u)\n", cmd);
            ret = -ENODEV;
            break;
    }
    return ret;
}

const struct file_operations g_dms_file_operations = {
    .owner = THIS_MODULE,
    .open = dms_open,
    .release = dms_release,
    .poll = dms_msg_poll,
    .unlocked_ioctl = dms_ioctl,
};

STATIC int __init urd_init(void)
{
    int ret;

    dms_info("dms urd driver init start.\n");
    ret = drv_davinci_register_sub_module(DAVINCI_INTF_MODULE_DMS, &g_dms_file_operations);
    if (ret != 0) {
        dms_err("Register DMS module failed. (ret=%d)\n", ret);
        return ret;
    }

    ret = dms_kv_init();
    if (ret) {
        dms_err("init dms kv failed. (ret=%d)\n", ret);
        goto unregister_sub_module;
    }

    dms_info("dms urd driver init success.\n");
    return 0;

unregister_sub_module:
    (void)drv_ascend_unregister_sub_module(DAVINCI_INTF_MODULE_DMS);
    return ret;
}

STATIC void __exit urd_exit(void)
{
    int ret;

    dms_info("Dms urd exit start.\n");
    ret = drv_ascend_unregister_sub_module(DAVINCI_INTF_MODULE_DMS);
    if (ret != 0) {
        dms_err("Unregister DMS module failed! (ret=%d)\n", ret);
        return;
    }

    dms_kv_uninit();
    dms_info("Dms urd driver exit success.\n");
    return;
}

module_init(urd_init);
module_exit(urd_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Huawei Tech. Co., Ltd.");
MODULE_DESCRIPTION("DAVINCI driver");
